/**
 * @author franky.ffy
 */
package com.esdk.utils;

import cn.hutool.core.util.ArrayUtil;
import com.esdk.esdk;
import com.esdk.interfaces.Callback;
import lombok.NonNull;

import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class EasyScheduler{
	private EasyScheduler self;
	private long esid;
	private String taskName;
	private Thread timer;
	private Object task;
	private static short lastTimerIndex=-1;
	private boolean isInterrupted=false,isPause=false;
	private long lastRunTimestamp;
	private long lastRunElapsed;
	private Response lastRunResult;
	private boolean isRuning;
	private Callback startEvent;
	private Callback finishEvent;
	private int runCount=0;
	private int runErrorCount=0;
	final private String[] pattern=new String[7];
	//	private static ExecutorService executorService = Executors.newFixedThreadPool(10);
//	private static ExecutorService executorService= Executors.newVirtualThreadPerTaskExecutor();
	private static ExecutorService executorService=new ThreadPoolExecutor(ThreadUtils.CorePoolSize,ThreadUtils.MaximumPoolSize,ThreadUtils.DefaultTimeOutSec,TimeUnit.SECONDS
		,new LinkedBlockingQueue<>(),Thread.ofVirtual().name("easyschedule-",1).factory());

	public EasyScheduler(final Runnable runable,final String schedulePattern){
		this("EasyScheduler"+(lastTimerIndex++),runable,schedulePattern);
	}

	public EasyScheduler(String taskName,final Runnable runable,@NonNull final String schedulePattern) {
		go(taskName,runable,schedulePattern);
	}
	public EasyScheduler(final Callable<Response> callable,@NonNull final String schedulePattern) {
		this("EasyScheduler"+(lastTimerIndex++),callable,schedulePattern);
	}
	public EasyScheduler(String taskName,final Callable<Response> callable,@NonNull final String schedulePattern) {
		this(taskName,0,callable,schedulePattern);
	}
	public EasyScheduler(String taskName,int runCount,final Callable<Response> callable,@NonNull final String schedulePattern) {
		setRunCount(runCount);
		go(taskName,callable,schedulePattern);
	}
	public EasyScheduler setId(long id){
		this.esid=id;
		return this;
	}

	public EasyScheduler setTaskName(@NonNull String taskname){
		this.taskName=taskname;
		this.timer.setName(taskname);
		return this;
	}

	public String getTaskName(){
		return this.taskName;
	}

	public long getId(){
		return this.esid;
	}

	/**
	 * 注意：程序如果运行时间过长，导致超过下次执行时间，只会错过，不会积累。
	 * */
	private void go(@NonNull String taskName,@NonNull final Object task,@NonNull final String schedulePattern){
		this.task=task;
		self=this;
		setPattern(schedulePattern);
		final int second = 0;
		final int minute = 1;
		final int hour = 2;
		final int date = 3;
		final int month = 4;
		final int day = 5;
		final int year = 6;
		timer = new Thread(taskName) {
			@Override
			public void run() {
				while (!isInterrupted && !isInterrupted()) {
					if(!isPause){
						Calendar c=Calendar.getInstance();
						c.setTime(new Date());
						if(isRange(pattern[year],c.get(Calendar.YEAR))
							&&isRange(pattern[month],c.get(Calendar.MONTH)+1) // 月份要加1，如7月1日用c.get(Calendar.MONTH)等于6
							&&isRange(pattern[day],c.get(Calendar.DAY_OF_WEEK)) //周日=1，周一=2，...周六=7
							&&isRange(pattern[date],c.get(Calendar.DATE))
							&&isRange(pattern[hour],c.get(Calendar.HOUR_OF_DAY))
							&&isRange(pattern[minute],c.get(Calendar.MINUTE))
							&&isRange(pattern[second],c.get(Calendar.SECOND))){
							runTask();
						}
					}
					long end=System.currentTimeMillis();
					long sleepTime = 1000 - end % 1000;
//					esdk.sout("end:{},sleepTime:{}",end,sleepTime);
          try{
            Thread.sleep(sleepTime);
          }catch(InterruptedException e){
						if(!isInterrupted)
            	throw new RuntimeException(e);
						else
							esdk.sout("定时器["+this.getName()+"]已停止");
          }
        }
			}
		};
		timer.setDaemon(true);
		timer.start();
	}

	public EasyScheduler runTask(){
		lastRunTimestamp=System.currentTimeMillis();
		if(startEvent!=null)
			startEvent.invoke(self);
			Future<Response> future=task instanceof Callable callable? executorService.submit(callable):(Future<Response>)executorService.submit((Runnable)task);
			isRuning=true;
			try{
				Response res=future.get();
				lastRunElapsed=System.currentTimeMillis()-lastRunTimestamp;
				lastRunResult=res!=null? res: new Response(false,"返回结果为Null");
				if(finishEvent!=null)
					finishEvent.invoke(self);
			}catch(Exception e){
				runErrorCount++;
				lastRunResult=new Response(false,e.toString());
				esdk.error(e);
			}finally{
				isRuning=false;
				runCount++;
			}
		return this;
	}

	public EasyScheduler interrupt(){
		timer.interrupt();
		isInterrupted=true;
		return this;
	}

	public EasyScheduler onStart(Callback<EasyScheduler> handleStartEvent){
		startEvent=handleStartEvent;
		return this;
	}

	public EasyScheduler onFinish(Callback<EasyScheduler> handleFinishEvent){
		finishEvent=handleFinishEvent;
		return this;
	}

	/**暂停定时器*/
	public void pause(){
		isPause=true;
	}

	public boolean isPause(){
		return isPause;
	}

	public boolean isInterrupted(){
		return isInterrupted;
	}

	/**取消暂停定时器*/
	public void resume(){
		isPause=false;
	}

	private String[] fill(String[] s){
		if(s.length<7){
			String[] result=new String[7];
			System.arraycopy(s,0,result,0,s.length);
			for(int i=0;i<result.length;i++){
				if(result[i]==null)
					result[i]="*";
			}
			return result;
		}
		return s;
	}

	
	/** 
	 * 秒 分 时 日 月 星期 年。与Quarz的时间表达式是一致的。 
	 * x/y表达一个等步长序列，x为起始值，y为增量步长值。如在分钟字段中使用0/15，则表示为0,15,30和45秒，而5/15在分钟字段中表示5,20,35,50
	 * */
	private boolean isRange(String range,int value){
		if(range.equals("*")||range.equals("?"))
			return true;
		else if(range.matches("\\d+"))
			return Integer.valueOf(range).intValue()==value;
		else if(range.indexOf(",")>=0){
			return ArrayUtil.contains(EasyMath.toIntArray(range.split(",")),value);
		}else if(range.matches("\\d+\\-\\d+")){
			Matcher m=Pattern.compile("(\\d+)\\-(\\d+)").matcher(range);
			if(m.matches()){
				return Integer.valueOf(m.group(1))<=value&&Integer.valueOf(m.group(2))>=value;
			}else
				return false;
		}else if(range.indexOf("/")>=0){
			String[] array=range.split("/");
			Integer start=EasyMath.toInt(array[0]),step=EasyMath.toInt(array[1]);
			boolean result=(value==start)||(value>start&&(value-start)%step==0);
			return result;
		}
		return false;
	}

	public void setPattern(String schedulePattern){
		String[] p = fill(schedulePattern.split(" +"));
		for(int i=0;i<pattern.length&&i<p.length;i++){
			pattern[i]=p[i];
		}
	}
	public Date getLastRunTime(){
		return new Date(lastRunTimestamp);
	}

	public long getLastRunElapsed(){
		return lastRunElapsed;
	}

	public Response getLastRunResult(){
		return lastRunResult;
	}

	public boolean isRuning(){
		return isRuning;
	}

	public EasyScheduler setRunCount(int count){
		runCount=count;
		return this;
	}

	public int getRunCount(){
		return runCount;
	}

	public int getRunErrorCount(){
		return runErrorCount;
	}
	public static void main(String[] args) throws Exception{
		esdk.sout("schedule is start");
		Runnable runable=()->{
      esdk.sout("sleeping");
      esdk.tool.sleep(1000);
			esdk.sout("sleep 1000");
      esdk.sout(new Date().getTime());
    };

//		new EasyScheduler(runable,"0 0/5 0-23 * * 1-7 2009,2010,2011,2012,2013"); // 秒 分 时 日 月 星期 年。与Quarz的时间表达式是一致的,即表示2009年至2013年，周日至六，每小时每隔5分钟的第0秒，会触发任务执行。
//		new EasyScheduler(runable,"0 42 23 * * 2-6"); // 每周一执行
		EasyScheduler es=new EasyScheduler(runable,"* * * * * *"); // 每秒执行
		es.setTaskName("runnable测试任务");
		es.onFinish(es1->{
			esdk.sout("runnable任务执行时间：",esdk.time.format(es1.getLastRunTime()));
			esdk.sout("runnable任务执行时长：",esdk.time.formatElapse(es1.getLastRunElapsed()));
			esdk.sout("runnable任务执行次数：",es1.getRunCount());
			esdk.sout("runnable任务执行结果：",es1.getLastRunResult());
		});
		esdk.tool.sleep(3000);
		es.setPattern("0/10 * * * * *");
		es.interrupt();


		Callable callable=(Callable<Response>)()->{
			esdk.tool.sleep(500);
			return new Response(true,"当前时间："+esdk.time.getNowTime(esdk.time.DATETIME_FORMAT));
		};
		EasyScheduler es1=new EasyScheduler("callable测试任务",callable,"* * * * * *"); // 每秒执行
		es1.onStart(es22->esdk.sout("callable任务开始执行时间：",esdk.time.format(es22.getLastRunTime())));
		es1.onFinish(es2->{
      esdk.sout("callable任务执行时间：",esdk.time.format(es2.getLastRunTime()));
      esdk.sout("callable任务执行时长：",esdk.time.formatElapse(es2.getLastRunElapsed()));
      esdk.sout("callable任务执行次数：",es2.getRunCount());
      esdk.sout("callable任务执行结果：",es2.getLastRunResult());
    });
		esdk.tool.sleep(3000);
		es1.interrupt();
	}

}
